#include "lib/decoders/oggvorbis/pvmvoiceoggvorbis.h"

#include "lib/memory/pvmmemory.h"

PVMVoiceOggVorbis* PVMVoiceOggVorbis::Create(PVMOVDataSource* dataSource, int /*samplesPerSecond*/, int bitsPerSample, int /*chanelsNum*/)
{
	PVMVoiceOggVorbis* voice = new PVMVoiceOggVorbis();

	if(!voice)
		return NULL;

	if(!voice->Init(dataSource, bitsPerSample))
	{
		delete( voice);
		voice = NULL;
	}

	return voice;
}

PVMVoiceOggVorbis::PVMVoiceOggVorbis()
{
	vInfo = NULL;
	paused = false;
	bitstream = 0;
	isAlive = false;
	loopCount = 0;
	loopStart = loopEnd = -1;
	offsetInSamples = 0;
	totalLengthInSamples = -1;
}

PVMVoiceOggVorbis::~PVMVoiceOggVorbis()
{
	if(dataSource)
		dataSource->Release();
	dataSource = NULL;
	vInfo = NULL;
	paused = false;
	loopCount = 0;
}

bool PVMVoiceOggVorbis::Init(PVMOVDataSource* source, int bitsPerSample)
{
	dataSource = source;
	dataSource->AddRef();
	vInfo = ov_info(&(dataSource->OVFile()), -1);
	this->bitsPerSample = bitsPerSample;
	isAlive = true;
	loadTotalLengthInSamples();

#if defined(IW_PLATFORM_S3E)
	//Dirty hack to minimize memory allocations during FillBuffer :(
	if(totalLengthInSamples > 32)
	{
		ov_pcm_seek(&(dataSource->OVFile()), totalLengthInSamples - 16);
		ov_pcm_seek(&(dataSource->OVFile()), 0);
	}
#endif
	return true;
}

void PVMVoiceOggVorbis::GetFormat(pvm_uint32 &samplesPerSecond, pvm_uint32 &bitsperSample, pvm_uint32 &chanels)
{
	samplesPerSecond = bitsperSample = chanels = 0;
	if(vInfo!=NULL)
	{
		samplesPerSecond = vInfo->rate;
		chanels = vInfo->channels;
		bitsperSample = this->bitsPerSample;
	}
}

pvm_uint32 PVMVoiceOggVorbis::GetSamplesPerSecond()
{
	if(vInfo==NULL)
		return 0;

	return vInfo->rate;
}

pvm_uint32 PVMVoiceOggVorbis::GetBitsPerSample()
{
	return bitsPerSample;
}

pvm_uint32 PVMVoiceOggVorbis::GetChanelsNum()
{
	if(vInfo==NULL)
		return 0;

	return vInfo->channels;
}

void PVMVoiceOggVorbis::SetLoopCount(pvm_int32 loopCount)
{
	this->loopCount = loopCount;
}

pvm_int32 PVMVoiceOggVorbis::GetLoopCount()
{
	return loopCount;
}

pvm_bool PVMVoiceOggVorbis::SetLoopInSamples(pvm_int64 loopFrom, pvm_int64 loopTo)
{
	loadTotalLengthInSamples();
	if(totalLengthInSamples<0)
		return false;

	if(loopFrom>=totalLengthInSamples ||
		loopTo>=totalLengthInSamples)
	{
		return false;
	}

	this->loopStart = loopFrom;
	this->loopEnd = loopTo;

	return true;
}

pvm_bool PVMVoiceOggVorbis::GetLoopInSamples(pvm_int64 &loopFrom, pvm_int64 &loopTo)
{
	loopFrom = this->loopStart;
	loopTo = this->loopEnd;
	return true;
}

pvm_bool PVMVoiceOggVorbis::GetLoopInMS(pvm_int32 &loopFrom, pvm_int32 &loopTo)
{
	int devider = GetChanelsNum()*GetSamplesPerSecond();
	loopFrom = (pvm_int32)(this->loopStart*1000/devider);
	loopTo = (pvm_int32)(this->loopEnd*1000/devider);
	return true;
}

void PVMVoiceOggVorbis::SetPause(pvm_bool pause)
{
	this->paused = pause;
}

pvm_bool PVMVoiceOggVorbis::GetPause()
{
	return paused;
}

pvm_uint32 PVMVoiceOggVorbis::FillBuffer(void *buffer, pvm_uint32 numSamples)
{
	if(loopCount==0)
		return 0;

	pvm_uint32 samplesToRender = numSamples;
	char *sampleBuffer = (char*)buffer;
	long bytesRead;
	pvm_uint32 prevSamplesToRender;
	pvm_uint32 sampleSizeInBytes = bitsPerSample>>3;

	while(samplesToRender)
	{
		prevSamplesToRender = samplesToRender;
			//decode vorbis file
		bytesRead = decode((char*)sampleBuffer, samplesToRender, sampleSizeInBytes);
		if(bytesRead==0)
		{
			return numSamples - samplesToRender;
		}
		else if(bytesRead<0)
		{
			return numSamples - samplesToRender;
		}
		samplesToRender -= bytesRead/sampleSizeInBytes;
		sampleBuffer += bytesRead;
	}

	return numSamples - samplesToRender;
}

pvm_bool PVMVoiceOggVorbis::IsAlive()
{
	if(loopCount==0)
		return false;
	return isAlive;
}

void PVMVoiceOggVorbis::DoDestroy()
{
	delete( this);
}

void PVMVoiceOggVorbis::OnIdle()
{

}

#ifdef PVM_USE_TREMOR

long pvm_ov_read(OggVorbis_File *vf, char *buffer, int length, int /*bigendianp*/, int /*word*/, int /*sgned*/, int *bitstream)
{
	return ov_read(vf,buffer,length,bitstream);
}
#else
#define pvm_ov_read ov_read
#endif

long PVMVoiceOggVorbis::decode(char *buffer, int lengthInSamples, int sampleSizeInBytes)
{
	int length = lengthInSamples*sampleSizeInBytes;
	char *bufPtr = buffer;
	int sizeToCopy = length;
	long nread=0;
	while(sizeToCopy)
	{
		pvm_bool forceLoopFlag = false;
		int nSamplesFill = sizeToCopy/sampleSizeInBytes;

		if(loopStart >=0 && loopEnd>=0 && loopCount!=1)
		{
			if(offsetInSamples + nSamplesFill >loopStart)
			{
				loadTotalLengthInSamples();
				if(totalLengthInSamples>0)
				{
					nSamplesFill = (pvm_int)(loopStart - totalLengthInSamples);
				}
			}
		}

		if(nSamplesFill>0)
		{
			sizeToCopy = nSamplesFill*sampleSizeInBytes;
			nread = pvm_ov_read(&(dataSource->OVFile()), bufPtr, sizeToCopy, 0, sampleSizeInBytes, 1, &bitstream);
		}
		else
			nread = 0;

		if(nread>=0)
		{
			offsetInSamples += nread/sampleSizeInBytes;
			if(offsetInSamples == loopStart && loopCount!=1)
			{
				forceLoopFlag = true;
			}
		}

		if(nread==0 || forceLoopFlag)
		{
			bool reSeek = false;
			if(loopCount<0||loopCount>0)
			{
				if(loopCount>0)
					loopCount--;
				if(loopCount!=0)
					reSeek = true;
			}

			if(reSeek)
			{
				pvm_int64 loopTo = (loopEnd>=0)?loopEnd:0;
				if(ov_pcm_seek(&(dataSource->OVFile()), loopTo)!=0)
				{
					loopCount = 0;
					isAlive = false;
				}
				else
				{
					offsetInSamples = ov_pcm_tell(&(dataSource->OVFile()));
				}
			}
			else
			{
				isAlive = false;
			}

			break;
		}
		else if(nread<=0)
		{
			//error in data
			break;
		}

		sizeToCopy -= nread;
		bufPtr += nread;
	}

	if(length - sizeToCopy==0)
	{
		return nread;
	}

	return length - sizeToCopy;
}

pvm_bool PVMVoiceOggVorbis::SetPositionMS(pvm_int32 position)
{
	bool seekRes = ov_time_seek(&(dataSource->OVFile()), position/1000) == 0;
	if(seekRes)
	{
		offsetInSamples = ov_pcm_tell(&(dataSource->OVFile()));
	}

	return seekRes;
}

pvm_int32 PVMVoiceOggVorbis::GetPositionMS()
{
	pvm_int32 samplesCached = 0;//cacheBuffer.byteInBuffer()/2;
	pvm_int64 pcm_time = ov_pcm_tell(&(dataSource->OVFile()));
	if(pcm_time==OV_EINVAL)
		return -1;
	pcm_time -= samplesCached;
	pvm_int32 ret = (pvm_int32)((((double)pcm_time)/vInfo->rate)*1000);
	return ret;
}

pvm_bool PVMVoiceOggVorbis::GetOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32* /*optionSize*/)
{
	return false;
}

pvm_bool PVMVoiceOggVorbis::SetOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32 /*optionSize*/)
{
	return false;
}

void PVMVoiceOggVorbis::loadTotalLengthInSamples()
{
	if(totalLengthInSamples<0)
		totalLengthInSamples = ov_pcm_total(&(dataSource->OVFile()), bitstream);
}
